package manager

import (
	"sort"
	"sync"

	"github.com/Kindling-project/chaos-backend/api"
	"github.com/Kindling-project/chaos-backend/tools/kubernetescli"
	"github.com/spf13/viper"
)

var DefaultTemplateMap *ChaosTemplateMap

func SetupChaosTemplate(configPath string) {
	DefaultTemplateMap = ReadTemplate(configPath)
}

type ChaosTemplateMap struct {
	ChaosActionMap    map[string]ChaosTemplate `mapstructure:"chaos_action_map"`
	ChaosTemplateList []ChaosTemplate          `mapstructure:"chaos_template_list"`

	chaosListCache sync.Map
}

type ChaosTemplate struct {
	ComplexAction     string            `mapstructure:"complex_action"`
	Describe          string            `mapstructure:"describe"`
	SupportServices   []string          `mapstructure:"support_services"`
	CheckEntryDefined bool              `mapstructure:"check_entry_defined"`
	Note              map[string]string `mapstructure:"note"`

	ChaoNumber int `mapstructure:"chaos_number"`
	// TODO Support user defined params
}

func (t *ChaosTemplate) CheckNote(instance string) string {
	if t.Note == nil {
		return ""
	}

	if note, ok := t.Note[instance]; ok {
		return note
	} else if note, ok := t.Note["common"]; ok {
		return note
	}

	return ""
}

func ReadTemplate(configPath string) *ChaosTemplateMap {
	var vip = viper.New()
	vip.SetConfigFile(configPath)
	if err := vip.ReadInConfig(); err != nil {
		return nil
	}

	templateMap := ChaosTemplateMap{}
	if err := vip.Unmarshal(&templateMap); err != nil {
		// TODO Error Info
		return nil
	}

	templateMap.ChaosActionMap = make(map[string]ChaosTemplate)

	for index, item := range templateMap.ChaosTemplateList {
		templateMap.ChaosActionMap[item.ComplexAction] = item
		item.ChaoNumber = index
	}
	return &templateMap
}

func (ctm *ChaosTemplateMap) AddChaosItemFromTemplate(chaos_list []api.Chaos, chaosTemplate ChaosTemplate, instance string) []api.Chaos {
	if chaosTemplate.CheckEntryDefined {
		if _, ok := kubernetescli.DefaultServiceMethodEntryMap.CheckService(instance); !ok {
			return chaos_list
		}
	}

	if len(chaosTemplate.SupportServices) > 0 {
		var supported bool = false
		for _, service := range chaosTemplate.SupportServices {
			if service == instance {
				supported = true
				break
			}
		}

		if !supported {
			return chaos_list
		}
	}

	chaos_list = append(chaos_list, api.Chaos{
		Desc:          chaosTemplate.Describe,
		ComplexAction: chaosTemplate.ComplexAction,
		Params: []api.ChaosParams{
			{Name: "instance", Type: "string"},
		},
		Note:         chaosTemplate.CheckNote(instance),
		ActionNumber: chaosTemplate.ChaoNumber,
	})

	return chaos_list
}

func (ctm *ChaosTemplateMap) SearchForUnsatisfiedNote(instance string, action string) (note string, ok bool) {
	if template, ok := ctm.ChaosActionMap[action]; ok && template.Note != nil {
		note, ok = template.Note[instance]
		return note, ok
	}

	return "", false
}

func (ctm *ChaosTemplateMap) AddNoteAfterInjected(chaos *api.Chaos) {
	var instance string

	if chaos == nil || chaos.Params == nil {
		return
	}

	for _, param := range chaos.Params {
		if param.Name == "instance" {
			instance = param.Value
			break
		}
	}

	if instance == "" {
		return
	}

	note, ok := ctm.SearchForUnsatisfiedNote(instance, chaos.ComplexAction)

	if ok {
		chaos.Note = note
	}
}

func (ctm *ChaosTemplateMap) ListChaosTemplate(instance string) []api.Chaos {
	if chaosList, ok := ctm.chaosListCache.Load(instance); ok {
		return chaosList.([]api.Chaos)
	}

	chaos_list := make([]api.Chaos, 0)

	for _, item := range ctm.ChaosTemplateList {
		chaos_list = ctm.AddChaosItemFromTemplate(chaos_list, item, instance)
	}

	sort.Slice(chaos_list, func(i, j int) bool {
		return chaos_list[i].ActionNumber < chaos_list[j].ActionNumber
	})

	ctm.chaosListCache.Store(instance, chaos_list)
	return chaos_list
}

func AddNoteAfterInjected(chaos *api.Chaos) {
	DefaultTemplateMap.AddNoteAfterInjected(chaos)
}

func ListChaosTemplate(instance string) []api.Chaos {
	return DefaultTemplateMap.ListChaosTemplate(instance)
}
